/* Arduino Uno SSTV PD90 dynamic screen display encoder using a
 * VS1003-based audio module
 * --
 * Chris Baird, July 2022 <vk2cjb@gmail.com>
 * Usage as per the GPLv3.
 */
/* based on vs1003_hello created 2012-04-05 by Andrey Karpov
 *
 * VS1003 development board connected thusly to the Arduino Mega:
 * [GND, 5V]
 * XDCS - D6
 * DREQ - D7
 * XRES - D8
 * XCS  - D9
 * SI   - D11
 * SO   - D12
 * SCLK - D13
 */

#include <math.h>		/* for fmodf() */
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <Arduino.h>
#include "VS1003.h"
#include <SPI.h>
#include "sine.h"
#include "charrom.h"

VS1003 player(9, 6, 7, 8); // cs_pin, dcs_pin, dreq_pin, reset_pin

#define VISCODE (99)            // PD90
#define scanlen (1362)          // 170.240 ms in audio samples

#define SCREENX 32
#define SCREENY 16
uint8_t charscreen[SCREENX*SCREENY];
uint16_t background_cr = 1900, background_cb = 2100;
uint16_t text_on = 2300, text_off = 1750;
const char hexdigits[] = "0123456789ABCDEF";

#define samplebytes (2)
#define channels (1)
#define samplerate (8000)
#define datarate (samplerate*channels*samplebytes)
#define TONESIZE (16)	      // using anything else causes distortion
uint16_t tonebuf[TONESIZE];

const uint8_t wavheader[] =
  {
    'R','I','F','F',
    0,0,0,0,                     // wav size (0=play indefinitely)
    'W','A','V','E', 'f','m','t',' ',
    16, 0, 0, 0,                 // fmt_chunk_size
    1, 0,                        // audio format = PCM
    1, 0,                        // num_channels
    samplerate&255, (samplerate>>8)&255, samplerate>>16,0,
    datarate&255, (datarate>>8)&255, datarate>>16,0,
    samplebytes, 0,              // sample_alignment
    samplebytes<<3, 0,
    'd','a','t','a',
    0,0,0,0                      // data size (0=infinite)
  };

// ----------------------------------------------------------------------

void docls (void)
{
  for (int i = 0; i < sizeof(charscreen); i++) charscreen[i] = 32;
}

// ----------------------------------------------------------------------

void setup ()
{
  Serial.begin(9600);
  while (!Serial) continue;
  SPI.begin();
  player.begin();
  player.setVolume(0);
  delay(4500);			// hack to fix power-up drifting
  player.playChunk(wavheader,sizeof(wavheader));
  docls();
  Serial.println("Ready.");
}

// ----------------------------------------------------------------------

void sampleout (uint16_t freq)
{
  static float iphase = 0;
  static int bp = 0;

  iphase += ((float)SINESIZE*(float)freq) / (float)samplerate;
  iphase = fmodf(iphase,SINESIZE);
  tonebuf[bp] = pgm_read_word(&sine[(int)iphase]);
  if (++bp == TONESIZE)
    {
      player.playChunk((const uint8_t*)tonebuf, sizeof(tonebuf));
      bp = 0;
    }
}


void tonegen (float freq, uint16_t samples)
{
  while (samples--) sampleout(freq);
}


void vis(uint8_t c)
{
  uint16_t i, p = 0;

  tonegen(1900,2400);		// 300ms
  tonegen(1200,80);		// 10ms
  tonegen(1900,2400);		// 300ms
  tonegen(1200,240);		// 30ms

  for (i = 1; i != 256; i <<= 1)
    {
      if ((i == 128) && (p & 1)) c |= 128;
      if (c & i)
	{
	  p++;
	  tonegen(1100,240);
	}
      else tonegen(1300,240);
    }
  tonegen(1200,240);
}

// ----------------------------------------------------------------------

void doline(int y)
{
  int y1 = y >> 4, p = (y>>4) * SCREENX, xt = 0, i;
  uint8_t c = 0, bm = 0, ch;
  uint16_t tf = 0, xx = 0, t;

  for (uint16_t x = 0; x < scanlen; x++)
    {
      if (tf >= 256)
	{
	  bm >>= 1;
	  tf -= 256;
	  xx++;
	}
      tf += ((uint32_t)256 << 8) / scanlen;

      if (bm == 0)
	{
	  if ((xt < 0) || (xt >= SCREENX) || (y1 < 0) || (y1 >= SCREENY))
	    c = 0;
	  else
	    {
	      ch = charscreen[p++];
	      i = (ch << 3) + ((y >>1) & 7);
	      c = (uint8_t)pgm_read_byte(&charrom[i]);
	    }
	  xt++;
	  bm = 128;
	}

      t = text_off;
      int yy = y << 1;
      if (c & bm) t = text_on;
      sampleout(t);
    }
}

// ----------------------------------------------------------------------

void txscreen (void)
{
  tonegen(1000,samplerate);
  tonegen(0,samplerate>>4);
  vis (VISCODE);

  for (int y = 0; y < 256; y += 2)
    {
      tonegen(1200, 160);
      tonegen(1500, 17);
      doline(y);
      tonegen(background_cr,scanlen);
      tonegen(background_cb,scanlen);
      doline(y+1);
    }
  tonegen(0,samplerate);
  Serial.println("END");
}

// ----------------------------------------------------------------------

int hextobin(char c)
{
  int r;
  for (r = 15; r != -1; r--) if (c == hexdigits[r]) break;
  return r;
}

void loop()
{
  char buffer[4];

  while (Serial.available() < 1) continue;

  Serial.readBytes(buffer, 1);
  switch (buffer[0])
    {
    case 'Z':
      docls();
      break;

    case 'T':
      txscreen();
      break;

    case ':':
      unsigned char y, x;
      while (Serial.available() < 4) continue;
      Serial.readBytes(buffer, 4);
      y = (hextobin(buffer[0]) << 4) + hextobin(buffer[1]);
      x = (hextobin(buffer[2]) << 4) + hextobin(buffer[3]);
      for (int p = (y*SCREENX) + x; p < SCREENX*SCREENY; p++)
	{
	  while (Serial.available() < 1) continue;
	  if (hextobin(Serial.peek()) < 0) break;
	  while (Serial.available() < 2) continue;
	  Serial.readBytes(buffer, 2);
	  charscreen[p] = (hextobin(buffer[0]) << 4) + hextobin(buffer[1]);
	}
      break;

    default: break;
    }
}

// ----------------------------------------------------------------------
